﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE;
using EnvDTE80;
using Expect.Core;
using Expect.VisualStudio.Extensions.Services;
using Expect.Core.Interfaces;

namespace Expect.VisualStudio.Extensions
{
    static class CodeModelHelpers
    {
        public static bool Implements(this CodeClass codeclass, Type implemented)
        {
            if (codeclass.ImplementedInterfaces.Implements(implemented))
            {
                return true;
            }

            if (codeclass.Bases.Implements(implemented))
            {
                return true;
            }

            return false;
        }

        public static void Filter(this Dictionary<string, List<ProjectItem>> classes, string classname, Type filter)
        {
            if (!string.IsNullOrEmpty(classname) && classes.ContainsKey(classname))
            {
                var lst = classes[classname];
                for (int i = lst.Count - 1; i >= 0; i--)
                {
                    var projectItem = lst[i];
                    var codeclass = (CodeClass)projectItem.FileCodeModel.CodeElements.GetClass(classname);
                    if (codeclass == null || !codeclass.Implements(filter))
                    {
                        lst.Remove(projectItem);
                    }
                }

                if (lst.Count == 0)
                {
                    classes.Remove(classname);
                }
            }
        }

        public static bool Implements(this CodeElements elements, Type implemented)
        {
            foreach (CodeElement codeElement in elements)
            {
                var kind = codeElement.Kind;

                if (kind == vsCMElement.vsCMElementInterface)
                {
                    CodeInterface2 eltInterface = (CodeInterface2)codeElement;
                    if (eltInterface.FullName == implemented.FullName)
                    {
                        return true;
                    }
                    var res = eltInterface.Bases.Implements(implemented);
                    if (res) return true;

                }
                else if (kind == vsCMElement.vsCMElementClass)
                {
                    CodeClass eltClass = (CodeClass)codeElement;
                    var res = eltClass.Implements(implemented);
                    if (res) return true;
                }
            }

            return false;
        }

        public static bool HasMethodWithXArgs(this CodeClass codeClass, string methodname, int numargs)
        {
            foreach (CodeElement2 elt in codeClass.Members)
            {
                if (elt.Kind == vsCMElement.vsCMElementFunction)
                {
                    CodeFunction2 function = (CodeFunction2)elt;
                    if (function.Name == methodname && function.Parameters.Count == numargs)
                    {
                        return true;
                    }
                }
            }

            foreach (CodeClass element in codeClass.Bases)
            {
                if (HasMethodWithXArgs(element, methodname, numargs)) return true;
            }

            return false;
        }

        public static void GetClassHints(this CodeElements elements, List<string> lst)
        {
            foreach (CodeClass class2 in elements.GetClasses())
            {
                class2.GetClassHints(lst);
            }
        }

        public static void GetClassHints(this CodeClass codeClass, List<string> lst)
        {
            string classname = codeClass.Name;
            codeClass.Bases.GetClassHints(lst);
            foreach (CodeAttribute2 attribute in codeClass.Attributes)
            {
                if (attribute.FullName == typeof(FixtureHintAttribute).FullName)
                {
                    string val = attribute.Value;
                    if (val.StartsWith("\"") && val.EndsWith("\""))
                    {
                        val = val.Substring(1, val.Length - 2);
                    }

                    lst.Add(ConformFixtureHint(val));
                }
            }
        }

        private static string ConformFixtureHint(string source)
        {
            source = source.Replace("\\\"", "\"");
            return source;
        }

        public static IEnumerable<ClassMap> ToClassMap(this IEnumerable<CodeClass> codeClasses)
        {
            foreach (var codeClass2 in codeClasses)
            {
                yield return codeClass2.ToClassMap();
            }
        }

        public static ClassMap ToClassMap(this CodeClass codeClass)
        {
            ClassMap res = new ClassMap();
            res.ClassName = codeClass.Name;
            res.FullName = codeClass.FullName;
            res.Item = codeClass.ProjectItem;
            res.IsFixture = codeClass.Implements(typeof(IFixtureImplementation));
            res.IsActorFixture = codeClass.Implements(typeof(IActorFixtureFormat));
            res.IsSpecification = codeClass.Implements(typeof(ISpecificationImplementation));
            res.IsContainer = codeClass.Implements(typeof(ISpecificationContainer));

            if (res.IsFixture || res.IsContainer || res.IsSpecification)
            {
                codeClass.GetClassHints(res.ClassHints);

                foreach (var elt in codeClass.ReadMethodHints())
                {
                    res.MethodsHints.Add(elt);
                }
            }

            return res;
        }

        public static IEnumerable<CodeClass> GetClasses(this Project project)
        {
            foreach (var codeClass2 in project.ProjectItems.GetClasses())
            {
                yield return codeClass2;
            }
        }

        public static IEnumerable<CodeClass> GetClasses(this ProjectItems items)
        {
            foreach (ProjectItem item in items)
            {
                if (item.FileNames[0].EndsWith(".cs"))
                {
                    if (!item.FileNames[0].EndsWith(".expect.cs"))
                    {
                        foreach (var codeClass2 in item.FileCodeModel.CodeElements.GetClasses())
                        {
                            yield return codeClass2;
                        }
                    }
                }
                else
                {
                    foreach (var codeClass2 in item.ProjectItems.GetClasses())
                    {
                        yield return codeClass2;
                    }
                }
            }
        }

        public static IEnumerable<CodeClass> GetClasses(this CodeElements elements)
        {
            foreach (CodeElement codeelt in elements)
            {
                if (codeelt is CodeClass)
                {
                    var eltclass = (CodeClass)codeelt;
                    yield return eltclass;
                }

                CodeElements childs = null;
                try
                {
                    childs = codeelt.Children;
                    int total = childs.Count;
                }
                catch
                {
                    childs = null;
                }

                if (childs != null)
                {
                    foreach (var class2 in childs.GetClasses())
                    {
                        yield return class2;
                    }
                }
            }
        }

        public static IEnumerable<string> ReadMethodHints(this CodeClass codeClass2)
        {
            string classname = codeClass2.Name;

            foreach (var codeClass in codeClass2.Bases.GetClasses())
            {
                foreach (var hint in codeClass.ReadMethodHints())
                {
                    yield return hint;
                }
            }

            CodeElements childs = null;
            try
            {
                childs = codeClass2.Children;
                int total = childs.Count;
            }
            catch (Exception)
            {
                childs = null;
            }

            if (childs != null)
            {
                foreach (CodeElement elt in childs)
                {
                    if (elt is CodeFunction2)
                    {
                        var meth = elt as CodeFunction2;
                        foreach (CodeAttribute2 attr in meth.Attributes)
                        {
                            if (attr.FullName == typeof(FixtureHintAttribute).FullName)
                            {
                                string val = attr.Value;
                                if (val.StartsWith("\"") && val.EndsWith("\""))
                                {
                                    val = val.Substring(1, val.Length - 2);
                                }
                                yield return ConformFixtureHint(val);
                            }
                        }
                    }
                }
            }

        }

        public static CodeClass GetClass(this CodeElements elements, string classname)
        {
            if (string.IsNullOrEmpty(classname)) return null;

            classname = classname.ToLower();
            foreach (CodeElement codeelt in elements)
            {
                if (codeelt is CodeClass)
                {
                    var eltclass = (CodeClass)codeelt;
                    string eltname = eltclass.Name.ToLower();
                    string eltfullname = eltclass.FullName.ToLower();
                    if (eltname == classname || eltfullname == classname) return eltclass;
                }
                var child = codeelt.Children.GetClass(classname);
                if (child != null) return child;
            }

            return null;
        }

        public static List<CodeFunction2> GetMethods(this CodeClass codeClass, INameProvider provider, string methodname)
        {
            List<CodeFunction2> res = new List<CodeFunction2>();
            GetMethods(codeClass, provider, methodname, res);
            return res;
        }

        private static void GetMethods(this CodeClass codeClass, INameProvider provider, string methodname, List<CodeFunction2> res)
        {            
            foreach (var elt in codeClass.Members)
            {
                if (elt is CodeFunction)
                {
                    if (((elt as CodeFunction2).Name == methodname))
                    {
                        res.Add(elt as CodeFunction2);
                        continue;
                    }

                    foreach (CodeAttribute2 attribute in (elt as CodeFunction2).Attributes)
                    {
                        if (attribute.FullName == typeof(FixtureHintAttribute).FullName)
                        {
                            string val = attribute.Value;
                            if (val.StartsWith("\"") && val.EndsWith("\""))
                            {
                                val = val.Substring(1, val.Length - 2);
                            }
                            var eltAlias = ConformFixtureHint(val);
                            FixtureRow row = new FixtureRow(eltAlias.Split('|'));
                            string eltName = provider.ElementName(row.NamesForFluent().ToArray());
                            if (eltName == methodname)
                            {
                                res.Add(elt as CodeFunction2);
                                break;
                            }
                        }
                    }
                }

            }

            try
            {
                foreach (var baseClass in codeClass.Bases.GetClasses())
                {
                    GetMethods(baseClass, provider, methodname, res);
                }
            }
            catch (Exception)
            {                
            }
        }

        public static CodeFunction2 HasFixtureMethodWithFixtureData(this CodeClass codeclass, INameProvider provider, string methodname, string[] methodargs)
        {
            var lst = codeclass.GetMethods(provider, methodname);
            if (lst.Count > 0)
            {
                foreach (var codeFunction in lst)
                {
                    if (codeFunction.Parameters.Count == methodargs.Length + 1)
                    {
                        CodeParameter prm = (CodeParameter)codeFunction.Parameters.Item(codeFunction.Parameters.Count);
                        if (prm.Type.AsString == typeof(FixtureData).FullName)
                        {
                            return codeFunction;
                        }
                    }
                }
            }

            return null;
        }

        public static CodeFunction2 HasFixtureMethod(this CodeClass codeclass, INameProvider provider, string methodname, string[] methodargs)
        {
            var lst = codeclass.GetMethods(provider, methodname);
            
            if (lst.Count > 0)
            {
                foreach (var codeFunction in lst)
                {
                    if (codeFunction.Parameters.Count == methodargs.Length)
                    {
                        return codeFunction;
                    }
                }
            }

            return null;
        }
    }
}
